﻿using System;
using System.IO;
using System.Linq;

namespace Innovative.ProXr
{
	public enum RelayId : byte
	{
		None = 0,
		Relay1 = 1,
		Relay2 = 2,
		Relay3 = 3,
		Relay4 = 4,
		Relay5 = 5,
		Relay6 = 6,
		Relay7 = 7,
		Relay8 = 8
	}

	public enum AutomaticRelayRefreshingStatus
	{
		On,
		Off,
		Unknown
	}

	public class RelayController
	{
		private IRelayDeviceConfiguration _configuration = null;

		public RelayController()
		{
		}

		public RelayController(IRelayDeviceConfiguration configuration)
		{
			this.Configuration = configuration;
		}

		/// <summary>
		/// Contains information about the relay controller used to connect and send commands.
		/// </summary>
		public IRelayDeviceConfiguration Configuration
		{
			get
			{
				return _configuration;
			}
			set
			{
				_configuration = value;
			}
		}

		/// <summary>
		/// Sends a command to the relay device specified in the RelayDeviceConfiguration.
		/// </summary>
		/// <param name="data">A RelayByte array containing the commands required for the given command set.</param>
		/// <returns>A RelayByte array containing the data returned by the relay device.</returns>
		public RelayByte[] SendCommand(RelayByte[] data)
		{
			RelayByte[] returnValue = new RelayByte[0];

			try
			{
				if (this.Configuration.Connect())
				{
					using (Stream io = this.Configuration.GetStream())
					{
						// ***
						// *** Send the command
						// ***
						io.Write(data.ToByteArray(), 0, data.Length);

						// ***
						// *** Wait for data to be available and read it
						// ***
						returnValue = this.ReadStream(io);

						if (returnValue.Length == 0)
						{
							throw new NoCommandResponseException();
						}

						// ***
						// *** Close the stream
						// ***
						io.Close();
					}
				}
			}
			finally
			{
				this.Configuration.Disconnect();
			}

			return returnValue;
		}

		/// <summary>
		/// Waits for data on the stream and then reads it.
		/// </summary>
		/// <param name="stream">An open stream ready to receive data.</param>
		/// <returns>A RelayByte array of data retrieved from the stream.</returns>
		internal RelayByte[] ReadStream(Stream stream)
		{
			RelayByte[] returnValue = new RelayByte[0];

			// ***
			// *** Read the data using asynchronously so that we can wait
			// *** for the response. The largest return value in the API is 256 bytes
			// ***
			byte[] readDataBuffer = new byte[512];
			IAsyncResult result = stream.BeginRead(readDataBuffer, 0, 512, null, null);
			result.AsyncWaitHandle.WaitOne(this.Configuration.CommandTimeout);
			int readCount = stream.EndRead(result);

			// ***
			// *** Copy only the bytes read to the returnValue
			// ***
			returnValue = new RelayByte[readCount];

			// ***
			// *** Copy only the updated portion of the buffer
			// *** based on readCount
			// ***
			byte[] secondaryBuffer = new byte[readCount];
			Array.Copy(readDataBuffer, secondaryBuffer, readCount);

			// ***
			// *** Query the secondary buffer and
			// *** convert the results to a RelayByte
			// ***
			var qry = from tbl in secondaryBuffer
					  select new RelayByte(tbl);

			// ***
			// *** Convert the query results to an array
			// ***
			returnValue = qry.ToArray();
			
			return returnValue;
		}
	}
}
